package com.order.service.impl;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.common.context.UserContextHolder;
import com.common.domain.Pages.PageResult;
import com.common.domain.pojo.Result;
import com.common.exception.order.OrderException;
import com.common.handler.RedisKeysHandler;
import com.common.rabbitmq.producer.core.CourseProducer;
import com.common.rabbitmq.producer.core.OrderProducer;
import com.order.domain.dto.CourseBuyDto;
import com.order.domain.dto.OrderPageDto;
import com.order.domain.dto.OrderPaymentDto;
import com.order.domain.dto.OrderRefundDto;
import com.order.domain.pojo.Order;
import com.order.domain.pojo.OrderDetail;
import com.order.domain.vo.OrderDetailVo;
import com.order.domain.vo.OrderListVo;
import com.order.enums.OrderOperationLogType;
import com.order.enums.OrderOperationType;
import com.order.enums.OrderStatus;
import com.order.feign.PaymentFeignClient;
import com.order.mapper.OrderMapper;
import com.order.service.IOrderDetailService;
import com.order.service.IOrderOperationLogService;
import com.order.service.IOrderReviewService;
import com.order.service.IOrderService;
import com.order.utils.OrderUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 订单表 服务实现类
 * </p>
 *
 * @author 小杰大人
 * @since 2025-09-29
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
    private final RedissonClient redissonClient;
    private final IOrderDetailService iOrderDetailService;
    private final TransactionTemplate transactionTemplate;
    private final PaymentFeignClient paymentFeignClient;
    private final IOrderReviewService iOrderReviewService;
    private final OrderProducer orderProducer;
    private final CourseProducer courseProducer;
    private final IOrderOperationLogService iOrderOperationLogService;
    @Value("${order.timeout}")
    private Long timeout;

    @Override
    public String generateOrder(CourseBuyDto courseBuyDto)  {
        String[] from = new String[1];
        Long buyUserId = UserContextHolder.getContext().getUsersId();
        // 1.1：判断是否存在未付款订单的有效订单
        List<Order> orders = baseMapper.querySelectOrderByUserId(buyUserId,OrderStatus.WAIT_PAY.getCode());
        if (orders != null && orders.size() > 0){
            throw new OrderException(OrderException.ORDER_EXIST);
        }
        // 1.2：判断是否已经购买当前课程
        long count = baseMapper.hasPurchasedCourse(courseBuyDto.getCourseId(), UserContextHolder.getContext().getUsersId());
        if (count > 0){
            throw new OrderException(OrderException.ORDER_EXIST_CURRENT_COURSE);
        }
        // 2: 创建订单
        // 2.1: 得到锁对象
        RLock lock = redissonClient.getLock(RedisKeysHandler.getGenerateOrder(buyUserId));
        try {
            // 2.2: 尝试获取锁 3秒等待时间
            boolean tryLock = lock.tryLock(3, TimeUnit.SECONDS);
            // 2.3: 获取锁失败则直接返回
            if (!tryLock){
                throw new OrderException(OrderException.DEFAULT_MESSAGE);
            }
            try {
                // 生成订单号
                String orderNo = OrderUtils.generateOrderNo();
                // 生成支付流水号
                Long paymentSerialNumber = OrderUtils.generatePaymentSerialNumber();
                transactionTemplate.executeWithoutResult(status -> {
                    // 3: 构建订单主体数据
                    Order order = Order.builder()
                            .orderNo(orderNo)   // 订单号
                            .userId(buyUserId)
                            .subject("E选课-购买《" + courseBuyDto.getCourseName() + "》")
                            .totalAmount(courseBuyDto.getTotalAmount())
                            .payAmount(courseBuyDto.getPayAmount())
                            .paymentType(courseBuyDto.getPaymentType())
                            .paymentSerialNumber(paymentSerialNumber)
                            .status(OrderStatus.WAIT_PAY.getCode())
                            .remark(courseBuyDto.getRemark())
                            .createTime(LocalDateTime.now())
                            .timeOut(LocalDateTime.now().plus(timeout, ChronoUnit.MINUTES))
                            .build();
                    boolean save = this.save(order);
                    iOrderOperationLogService.add(order.getId(),order.getOrderNo(), OrderOperationLogType.CREATE_ORDER,buyUserId, OrderOperationType.USER,courseBuyDto);
                    if (save) {
                        // 4: 构建订单详情数据
                        OrderDetail orderDetail = OrderDetail.builder()
                                .orderId(order.getId())
                                .orderNo(orderNo)
                                .courseId(courseBuyDto.getCourseId())
                                .courseName(courseBuyDto.getCourseName())
                                .courseCover(courseBuyDto.getCourseCover())
                                .userId(courseBuyDto.getUserId())
                                .userName(courseBuyDto.getUserName())
                                .oriPrice(courseBuyDto.getOriPrice())
                                .curPrice(courseBuyDto.getCurPrice())
                                .quantity(courseBuyDto.getQuantity())
                                .totalAmount(courseBuyDto.getPayAmount())
                                .build();
                        boolean saved = iOrderDetailService.save(orderDetail);
                        // 5: 通知支付服务构建支付主体
                        if (saved){
                            iOrderOperationLogService.add(order.getId(),order.getOrderNo(), OrderOperationLogType.START_PAY,buyUserId, OrderOperationType.USER,courseBuyDto);
                            // 补偿机制：订单生成成功，但支付服务调用失败，回滚
                            String payForm = getPaymentForm(order);
                            // 返回支付表单
                            from[0] = payForm;
                            // 6: 定时任务 不断检测订单是否已经支付
                            orderProducer.orderClickTask(order.getId());
                        }
                    }
                });
            }finally {
                lock.unlock();
            }
        }catch (InterruptedException e) {
            throw new OrderException(e.getMessage());
        }
        return from[0];
    }

    @Override
    public String toPayment(Long orderId) {
        Order order = this.getById(orderId);
        // 1: 判断订单是否存在
        if (order == null){
            throw new OrderException(OrderException.ORDER_NOT_EXIST);
        }
        // 2: 订单状态需要为未付款
        if (OrderStatus.WAIT_PAY.getCode() != order.getStatus()){
            throw new OrderException(OrderException.ORDER_STATUS_ERROR);
        }
        // 3: 判断订单是否已过期
        if (order.getTimeOut().isBefore(LocalDateTime.now())){
            throw new OrderException(OrderException.ORDER_TIME_OUT);
        }
        iOrderOperationLogService.add(order.getId(),order.getOrderNo(), OrderOperationLogType.START_PAY,order.getUserId(), OrderOperationType.USER,null);
        return getPaymentForm(order);
    }

    public String getPaymentForm(Order order) {
        if (order == null){
            throw new OrderException(OrderException.ORDER_NOT_EXIST);
        }
        OrderPaymentDto orderPaymentDto = convertOrderToOrderPaymentDto(order);
        Result<String> result = paymentFeignClient.initiatePayment(orderPaymentDto);
        if (result.getCode() != 200){
            throw new OrderException(result.getMessage());
        }
        // 返回支付表单
        String from = result.getData();
        if (StrUtil.isEmptyIfStr(from)){
            throw new OrderException(OrderException.DEFAULT_MESSAGE);
        }
        return from;
    }

    private OrderPaymentDto convertOrderToOrderPaymentDto(Order order) {
        OrderDetail orderDetail = iOrderDetailService.querySelectByOrderId(order.getId());
        OrderPaymentDto orderPaymentDto = new OrderPaymentDto();
        BeanUtils.copyProperties(order, orderPaymentDto);
        orderPaymentDto.setCourseId(orderDetail.getCourseId());
        orderPaymentDto.setCourseName(orderDetail.getCourseName());
        orderPaymentDto.setOriPrice(orderDetail.getOriPrice());
        orderPaymentDto.setCurPrice(orderDetail.getCurPrice());
        orderPaymentDto.setQuantity(orderDetail.getQuantity());
        return orderPaymentDto;
    }

    @Override
    @Transactional
    public void paySuccess(String orderNo) {
        Order order = this.getOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderNo, orderNo));
        if (order == null){
            throw new OrderException(OrderException.ORDER_NOT_EXIST);
        }
        if (OrderStatus.isPaymentSuccess(order.getStatus())){
            // 订单已支付
            return;
        }
        // 1：更新订单状态
        order.setStatus(OrderStatus.PAID.getCode());
        order.setPaymentTime(LocalDateTime.now());
        this.updateById(order);
        iOrderOperationLogService.add(order.getId(),order.getOrderNo(), OrderOperationLogType.PAY_SUCCESS,order.getUserId(), OrderOperationType.USER,order);
        Result<Boolean> booleanResult = paymentFeignClient.paymentSuccess(orderNo);
        // 2：调用支付服务支付成功
        // 2.1：补偿机制：订单修改成功，但支付服务修改失败，回滚
        if (booleanResult.getCode() != 200 || !booleanResult.getData()){
            log.error("支付服务调用失败：{}",booleanResult.getMessage());
            throw new OrderException();
        }
        // 发布延迟消息 订单状态修改为已完成
        orderProducer.sendDelay("order.paid.to.completed.tack", order.getOrderNo(), 60 * 1000);
    }

    /**
     * todo 可优化SQL
     */
    @Override
    public PageResult<OrderListVo> pageList(OrderPageDto orderPageDto) {
        if (orderPageDto.getPageOn() == null) {
            orderPageDto.setPageOn(1);
        }
        if (orderPageDto.getPageSize() == null){
            orderPageDto.setPageSize(10);
        }
        LocalDateTime startTime = null;
        LocalDateTime endTime = null;
        if (!StrUtil.isEmptyIfStr(orderPageDto.getStartTime()) && !StrUtil.isEmptyIfStr(orderPageDto.getEndTime())){
            // 起始时间：当天0点
            startTime = LocalDateTime.parse(orderPageDto.getStartTime() + " 00:00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            // 结束时间：当天23.59.59
            endTime = LocalDateTime.parse(orderPageDto.getEndTime() + " 23:59:59", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        }
        Page<Order> page = this.page(new Page<>(orderPageDto.getPageOn(), orderPageDto.getPageSize()),
                new LambdaQueryWrapper<Order>()
                        .eq(Order::getUserId, UserContextHolder.getContext().getUsersId())
                        .eq(orderPageDto.getStatus() != null, Order::getStatus, orderPageDto.getStatus())
                        .eq(!StrUtil.isEmptyIfStr(orderPageDto.getOrderNo()), Order::getOrderNo, orderPageDto.getOrderNo())
                        .between(startTime != null, Order::getCreateTime, startTime, endTime)
                        .orderByDesc(Order::getCreateTime)
        );
        return new PageResult<>(page.getTotal(), page.getRecords().stream().map(order -> {
            OrderListVo orderListVo = new OrderListVo();
            BeanUtils.copyProperties(order,orderListVo);
            if (OrderStatus.COMPLETED.getCode() == order.getStatus()){
                Boolean reviewed = iOrderReviewService.isReviewed(order.getId(), UserContextHolder.getContext().getUsersId());
                orderListVo.setIsReview(BooleanUtil.isTrue(reviewed));
            }
            return orderListVo;
        }).toList());
    }

    @Override
    public OrderDetailVo detailById(Long orderId) {
        Order order = this.getById(orderId);
        OrderDetail orderDetail = iOrderDetailService.getOne(new LambdaQueryWrapper<OrderDetail>().eq(OrderDetail::getOrderId, orderId));
        OrderDetailVo orderDetailVo = new OrderDetailVo();
        BeanUtils.copyProperties(orderDetail,orderDetailVo);
        orderDetailVo.setRemark(order.getRemark());
        orderDetailVo.setSubject(order.getSubject());
        orderDetailVo.setPaymentTime(order.getPaymentTime());
        orderDetailVo.setPayAmount(order.getPayAmount());
        orderDetailVo.setPaymentType(order.getPaymentType());
        orderDetailVo.setPaymentSerialNumber(order.getPaymentSerialNumber());
        orderDetailVo.setStatus(order.getStatus());
        // 剩余支付时间
        if (OrderStatus.WAIT_PAY.getCode() == order.getStatus()){
            orderDetailVo.setRemainingPaymentTime(order.getTimeOut().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() -
                    LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
        }
        // 退款中 =》 退款流程
        if (OrderStatus.REFUNDING.getCode() == order.getStatus()){
            Byte refundStatus = paymentFeignClient.queryRefundResult(order.getOrderNo()).getData();
            orderDetailVo.setRefundStatus(refundStatus);
        }
        return orderDetailVo;
    }

    @Override
    public List<Long> getPurchasedCourseIds(Long userId) {
        return baseMapper.getPurchasedCourseIdsByUserId(userId);
    }

    @Override
    @Transactional
    public void paidToCompleted(String orderNo) {
        // 1：获取订单
        Order order = baseMapper.querySelectByOrderNo(orderNo);
        if (order == null){
            throw new OrderException(OrderException.ORDER_NOT_EXIST);
        }
        // 2: 订单支付成功
        if (OrderStatus.COMPLETED.getCode() == order.getStatus()){
            return;
        }
        if (OrderStatus.PAID.getCode() != order.getStatus()){
            log.error("订单支付成功状态校验失败：订单ID：{},订单Status：{}",orderNo,order.getStatus());
            return;
        }
        // 3: 此时订单确保为支付成功状态 可更新为已完成
        order.setStatus(OrderStatus.COMPLETED.getCode());
        this.updateById(order);
        // 订单操作日志 订单完成
        iOrderOperationLogService.add(order.getId(),order.getOrderNo(), OrderOperationLogType.ORDER_COMPLETE,order.getUserId(), OrderOperationType.USER,order);
        // 4: 通知课程服务 销量 + 1
        courseProducer.increaseCourseSales(iOrderDetailService.getOne(new LambdaQueryWrapper<OrderDetail>().eq(OrderDetail::getOrderId, order.getId())).getCourseId());
    }

    @Override
    @Transactional
    public void cancel(Long orderId, boolean isSystem) {
        // 1: 数据校验
        Order order = this.getById(orderId);
        // 1.1：订单存在校验
        if (order == null){
            log.debug("订单关闭失败-订单不存在：订单ID：{}",orderId);
            throw new OrderException(OrderException.ORDER_NOT_EXIST);
        }
        // 1.2：订单状态校验（未支付）
        if (OrderStatus.WAIT_PAY.getCode() != order.getStatus()){
            log.debug("订单关闭失败-状态不符：订单ID：{}，status：{}",orderId,order.getStatus());
            return;
        }
        // 1.3: 订单是否已经关闭
        if (OrderStatus.CANCELED.getCode() == order.getStatus()){
            log.debug("订单关闭失败-订单已关闭：订单ID：{}",orderId);
            return;
        }
        // 1.4: 订单是否已经超时
        if (order.getTimeOut().isBefore(LocalDateTime.now())){
            log.debug("订单关闭失败-订单已超时：订单ID：{}",orderId);
            return;
        }
        // 2：订单取消逻辑
        // 2.1: 关闭订单
        Boolean result = paymentFeignClient.cancel(order.getOrderNo()).getData();
        if (BooleanUtil.isTrue( result)){
            order.setStatus(OrderStatus.CANCELED.getCode());
            this.updateById(order);
            OrderOperationLogType orderOperationLogType = isSystem ? OrderOperationLogType.SYSTEM_CANCEL : OrderOperationLogType.USER_CANCEL;
            OrderOperationType orderOperationType = isSystem ? OrderOperationType.SYSTEM : OrderOperationType.USER;
            iOrderOperationLogService.add(order.getId(),order.getOrderNo(), orderOperationLogType,order.getUserId(), orderOperationType,order);
        }
    }

    @Override
    @Transactional
    public void refund(Long orderId, String reason) {
        Order order = this.getById(orderId);
        if (order == null){
            throw new OrderException(OrderException.ORDER_NOT_EXIST);
        }
        if (OrderStatus.COMPLETED.getCode() != order.getStatus()){
            throw new OrderException(OrderException.ORDER_STATUS_ERROR);
        }
        order.setStatus(OrderStatus.REFUNDING.getCode());   // 状态修改为 退款中
        this.updateById(order);
        OrderRefundDto orderRefundDto = new OrderRefundDto();
        OrderPaymentDto orderPaymentDto = convertOrderToOrderPaymentDto(order);
        BeanUtils.copyProperties(orderPaymentDto,orderRefundDto);
        orderRefundDto.setReason(reason);
        orderRefundDto.setRefundNo(OrderUtils.generateRefundOrderNo());
        Result<Boolean> refund = paymentFeignClient.refund(orderRefundDto);
        if (refund.getCode() != Result.SUCCESS){
            throw new OrderException(OrderException.ORDER_REFUND_FAIL);
        }
        iOrderOperationLogService.add(order.getId(),order.getOrderNo(), OrderOperationLogType.START_REFUND,order.getUserId(), OrderOperationType.USER,order);
    }

    @Override
    public void refundSuccess(String orderNo) {
        Order order = baseMapper.querySelectByOrderNo(orderNo);
        OrderServiceImpl currentProxy = (OrderServiceImpl) AopContext.currentProxy();
        currentProxy.updateOrderStatus(orderNo, OrderStatus.REFUNDED);
        iOrderOperationLogService.add(order.getId(),order.getOrderNo(), OrderOperationLogType.REFUND_SUCCESS,order.getUserId(), OrderOperationType.USER,orderNo);
        // todo 短信 & 邮箱通知用户退款失败 =》发送到邮箱内
    }

    @Override
    public void refundFail(String orderNo) {
        Order order = baseMapper.querySelectByOrderNo(orderNo);
        OrderServiceImpl currentProxy = (OrderServiceImpl) AopContext.currentProxy();
        currentProxy.updateOrderStatus(orderNo, OrderStatus.COMPLETED);
        iOrderOperationLogService.add(order.getId(),order.getOrderNo(), OrderOperationLogType.REFUND_FAIL,order.getUserId(), OrderOperationType.USER,orderNo);
        // todo 短信 & 邮箱通知用户退款失败 =》发送到邮箱内
    }


    @Transactional
    public void updateOrderStatus(String orderNo, OrderStatus orderStatus){
        Order order = baseMapper.querySelectByOrderNo(orderNo);
        if (order == null){
            throw new OrderException(OrderException.ORDER_NOT_EXIST);
        }
        order.setStatus(orderStatus.getCode());
        this.updateById(order);
    }
}
